package com.jlt.baidu.utils;

import static org.opencv.imgproc.Imgproc.CHAIN_APPROX_SIMPLE;
import static org.opencv.imgproc.Imgproc.RETR_CCOMP;
import static org.opencv.imgproc.Imgproc.approxPolyDP;
import static org.opencv.imgproc.Imgproc.arcLength;
import static org.opencv.imgproc.Imgproc.contourArea;
import static org.opencv.imgproc.Imgproc.minAreaRect;

import java.util.ArrayList;
import java.util.List;

import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.MatOfPoint;
import org.opencv.core.MatOfPoint2f;
import org.opencv.core.Point;
import org.opencv.core.Rect;
import org.opencv.core.RotatedRect;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.imgproc.Imgproc;

import com.jlt.ocr.tesseract.OcrRectangle;

import lombok.experimental.UtilityClass;

/**
 * 对图片进行画出轮廓线条，以及涉及图片线条处理业务
 * 
 * @author Ives.Chen
 *
 */
@UtilityClass
public class OpencvLineUtil {
    /**
     * 根据腐蚀后区域，画出文字识别区域
     * 
     * @param source 开始边缘检测前最新的图片
     * @param encode 腐蚀后的图片
     * @return
     */
    public static List<OcrRectangle> commonImageLine(Mat source, Mat encode) {
        List<RotatedRect> rects = getCommonImageRect(encode);
        // 画出轮廓线
        List<OcrRectangle> reaList = new ArrayList<>();
        for (RotatedRect rect : rects) {
            Point[] rectPoint = new Point[4];
            rect.points(rectPoint);
            for (int j = 0; j <= 3; j++) {
                Imgproc.line(source, rectPoint[j], rectPoint[(j + 1) % 4], new Scalar(0, 255, 0), 1);
            }
            int top = rect.boundingRect().y;
            int left = rect.boundingRect().x;
            int width = rect.boundingRect().width;;
            int height = rect.boundingRect().height;
            OcrRectangle range = new OcrRectangle(left, top, width, height);
            reaList.add(range);
        }
        return reaList;
    }

    /**
     * 获的图片的轮廓线条点集合
     * 
     * @param source
     * @return
     */
    private static List<RotatedRect> getCommonImageRect(Mat source) {
        // 查找文字区域轮廓
        List<RotatedRect> rects = new ArrayList<>();
        List<MatOfPoint> contours = new ArrayList<>();
        Mat hierarchy = source.clone();
        Imgproc.findContours(source, contours, hierarchy, RETR_CCOMP, CHAIN_APPROX_SIMPLE, new Point(0, 0));
        for (int i = 0; i < contours.size(); i++) {
            // 计算当前轮廓的面积
            double area = contourArea(contours.get(i));
            // 面积小于1000的全部筛选掉
            if (area < 1000) {
                continue;
            }
            // 轮廓近似，作用较小，approxPolyDP函数有待研究
            MatOfPoint2f matOfPoint2f = new MatOfPoint2f(contours.get(i).toArray());
            double epsilon = 0.002 * arcLength(matOfPoint2f, true);
            approxPolyDP(matOfPoint2f, matOfPoint2f, epsilon, true);

            // 找到最小矩形，该矩形可能有方向
            RotatedRect rect = minAreaRect(matOfPoint2f);

            // 筛选那些太细的矩形，留下扁的 舍去高是宽的2倍
            if (rect.boundingRect().height > rect.boundingRect().width * 2) {
                continue;
            }
            // 舍去边缘的干扰矩形
            // 顶部5
            if (rect.boundingRect().y < 5) {
                continue;
            }
            // 底部20
            if (rect.boundingRect().y >= source.rows() - 20) {
                continue;
            }
            // 左侧5
            if (rect.boundingRect().x < 10) {
                continue;
            }
            // 右侧10
            if (rect.boundingRect().x > source.cols() - 10) {
                continue;
            }
            // 去除几乎覆盖整个图片的文本框
            if (rect.boundingRect().height >= source.rows() - 100) {
                continue;
            }
            // 符合条件的rect添加到rects集合中
            rects.add(rect);
        }
        return rects;
    }

    /**
     * 完成对图片内容去除表格内容业务
     * 
     * @param mat
     * @return
     */
    public static Mat imageDropExcelBusiness(Mat mat) {
        Mat first = Opencv420Util.imageGray(mat);
        // 图片二值化，去除背景，增强图片
        Mat mat1 = Opencv420Util.thresholdBlackGround(first.clone());
        // 膨胀+腐蚀:补全表格线内的空洞
        Mat element = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(3, 3));
        Imgproc.dilate(mat1, mat1, element);
        Imgproc.erode(mat1, mat1, element);
        // 获取图片上的横线
        Mat horizontalLine = getExcelHorizontalLine(mat1.clone());
        // 获取图片上的竖线
        Mat verticalLine = getExcelVerticalLine(mat1.clone());
        // 将横线和竖线合并为一张图片
        Mat tableLine = unionExcelAllLine(horizontalLine, verticalLine);
        // 通过 bitwise_and 定位横线、垂直线交汇的点
        Mat points_image = getExcelLineCross(horizontalLine, verticalLine);
        Mat dfsa = dropExcelArea(mat, tableLine, points_image, 0);
        return dfsa;
    }

    /**
     * 完成对图片内容去除非表格内容业务
     * 
     * @param mat
     * @return
     */
    public static Mat imageDropNotExcelBusiness(Mat mat) {
        Mat first = Opencv420Util.imageGray(mat);
        // 图片二值化，去除背景，增强图片
        Mat mat1 = Opencv420Util.thresholdBlackGround(first.clone());
        // 膨胀+腐蚀:补全表格线内的空洞
        Mat element = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(3, 3));
        Imgproc.dilate(mat1, mat1, element);
        Imgproc.erode(mat1, mat1, element);
        // 获取图片上的横线
        Mat horizontalLine = getExcelHorizontalLine(mat1.clone());
        // 获取图片上的竖线
        Mat verticalLine = getExcelVerticalLine(mat1.clone());
        // 将横线和竖线合并为一张图片
        Mat tableLine = unionExcelAllLine(horizontalLine, verticalLine);
        // 通过 bitwise_and 定位横线、垂直线交汇的点
        Mat points_image = getExcelLineCross(horizontalLine, verticalLine);
        Mat dfsa = dropExcelArea(mat, tableLine, points_image, 1);
        return dfsa;
    }

    /**
     * 得到表格横线并相连
     * 
     * @param adaptiveThreshold
     * @return
     */
    public static Mat getExcelHorizontalLine(Mat adaptiveThreshold) {
        int scale = 50;
        int horizontalsize = adaptiveThreshold.cols() / scale;
        Mat horizontalStructure = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(horizontalsize, 1));
        Imgproc.erode(adaptiveThreshold, adaptiveThreshold, horizontalStructure);
        Imgproc.dilate(adaptiveThreshold, adaptiveThreshold, horizontalStructure);
        return adaptiveThreshold;
    }

    /**
     * 得到表格竖线并进行相连
     * 
     * @param adaptiveThreshold
     * @return
     */
    public static Mat getExcelVerticalLine(Mat adaptiveThreshold) {
        int scale = 50;
        int verticalsize = adaptiveThreshold.rows() / scale;
        if (verticalsize == 0) {
            verticalsize = 1;
        }
        Mat verticalStructure = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(1, verticalsize));
        Imgproc.erode(adaptiveThreshold, adaptiveThreshold, verticalStructure);
        Imgproc.dilate(adaptiveThreshold, adaptiveThreshold, verticalStructure);
        return adaptiveThreshold;
    }

    /**
     * 表格横竖线合并成一张图
     * 
     * @param horizontal
     * @param vertical
     * @return
     */
    public static Mat unionExcelAllLine(Mat horizontal, Mat vertical) {
        Mat or = new Mat();
        Core.bitwise_or(horizontal, vertical, or);
        return or;
    }

    /**
     * 获取表格横竖线交叉的点
     * 
     * @param horizontal
     * @param vertical
     * @return
     */
    public static Mat getExcelLineCross(Mat horizontal, Mat vertical) {
        Mat cross = new Mat();
        Core.bitwise_and(horizontal, vertical, cross);
        return cross;
    }

    /**
     * 从原图去除表格区域内容，或者非表格内容
     * 
     * @param srcImage 原图
     * @param mask_image 横线竖线合并图
     * @param points_image 横线竖线交点图
     * @param type 类型 0 去除表格 1去除非表格
     * @return
     */
    public static Mat dropExcelArea(Mat srcImage, Mat mask_image, Mat points_image, int type) {
        Mat result = srcImage.clone();
        List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
        Mat hierarchy = new Mat();
        Imgproc.findContours(mask_image, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE, new Point(0, 0));

        List<MatOfPoint> contours_poly = contours;
        Rect[] boundRect = new Rect[contours.size()];

        // 循环所有找到的轮廓-点
        for (int i = 0; i < contours.size(); i++) {

            MatOfPoint point = contours.get(i);
            MatOfPoint contours_poly_point = contours_poly.get(i);

            /*
             * 获取区域的面积 第一个参数，InputArray contour：输入的点，一般是图像的轮廓点 第二个参数，bool oriented = false:表示某一个方向上轮廓的的面积值，顺时针或者逆时针，一般选择默认false
             */
            double area = Imgproc.contourArea(contours.get(i));
            // 如果小于某个值就忽略，代表是杂线不是表格
            if (area < 100) {
                continue;
            }

            /*
             * approxPolyDP 函数用来逼近区域成为一个形状，true值表示产生的区域为闭合区域。比如一个带点幅度的曲线，变成折线
             *
             * MatOfPoint2f curve：像素点的数组数据。 MatOfPoint2f approxCurve：输出像素点转换后数组数据。 double epsilon：判断点到相对应的line segment
             * 的距离的阈值。（距离大于此阈值则舍弃，小于此阈值则保留，epsilon越小，折线的形状越“接近”曲线。） bool closed：曲线是否闭合的标志位。
             */
            Imgproc.approxPolyDP(new MatOfPoint2f(point.toArray()), new MatOfPoint2f(contours_poly_point.toArray()), 3, true);

            // 为将这片区域转化为矩形，此矩形包含输入的形状
            boundRect[i] = Imgproc.boundingRect(contours_poly.get(i));

            // 找到交汇处的的表区域对象
            Mat table_image = points_image.submat(boundRect[i]);

            List<MatOfPoint> table_contours = new ArrayList<MatOfPoint>();
            Mat joint_mat = new Mat();
            Imgproc.findContours(table_image, table_contours, joint_mat, Imgproc.RETR_CCOMP, Imgproc.CHAIN_APPROX_SIMPLE);
            // 从表格的特性看，如果这片区域的点数小于4，那就代表没有一个完整的表格，忽略掉
            if (type == 0 && table_contours.size() < 4) {
                continue;
            } else if (type == 1 && table_contours.size() > 4) {
                continue;
            }
            // 将白色矩形覆盖至表格，实现删除图像中表格内容
            Imgproc.rectangle(result, boundRect[i].tl(), boundRect[i].br(), new Scalar(255, 255, 255), -1, 4, 0);
        }
        return result;
    }

    /**
     * 从原图抠出表格区域内容
     * 
     * @param srcImage 原图
     * @param mask_image 横线竖线合并图
     * @param points_image 横线竖线交点图
     * @param type 类型 0 去除表格 1去除非表格
     */
    public static List<Mat> singleExcelArea(Mat srcImage, Mat mask_image, Mat points_image, int type) {
        List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
        Mat hierarchy = new Mat();
        Imgproc.findContours(mask_image, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE, new Point(0, 0));

        List<MatOfPoint> contours_poly = contours;
        Rect[] boundRect = new Rect[contours.size()];

        List<Mat> tables = new ArrayList<Mat>();

        // 循环所有找到的轮廓-点
        for (int i = 0; i < contours.size(); i++) {

            MatOfPoint point = contours.get(i);
            MatOfPoint contours_poly_point = contours_poly.get(i);

            /*
             * 获取区域的面积 第一个参数，InputArray contour：输入的点，一般是图像的轮廓点 第二个参数，bool oriented = false:表示某一个方向上轮廓的的面积值，顺时针或者逆时针，一般选择默认false
             */
            double area = Imgproc.contourArea(contours.get(i));
            // 如果小于某个值就忽略，代表是杂线不是表格
            if (area < 100) {
                continue;
            }

            /*
             * approxPolyDP 函数用来逼近区域成为一个形状，true值表示产生的区域为闭合区域。比如一个带点幅度的曲线，变成折线
             *
             * MatOfPoint2f curve：像素点的数组数据。 MatOfPoint2f approxCurve：输出像素点转换后数组数据。 double epsilon：判断点到相对应的line segment
             * 的距离的阈值。（距离大于此阈值则舍弃，小于此阈值则保留，epsilon越小，折线的形状越“接近”曲线。） bool closed：曲线是否闭合的标志位。
             */
            Imgproc.approxPolyDP(new MatOfPoint2f(point.toArray()), new MatOfPoint2f(contours_poly_point.toArray()), 3, true);

            // 为将这片区域转化为矩形，此矩形包含输入的形状
            boundRect[i] = Imgproc.boundingRect(contours_poly.get(i));

            // 找到交汇处的的表区域对象
            Mat table_image = points_image.submat(boundRect[i]);

            List<MatOfPoint> table_contours = new ArrayList<MatOfPoint>();
            Mat joint_mat = new Mat();
            Imgproc.findContours(table_image, table_contours, joint_mat, Imgproc.RETR_CCOMP, Imgproc.CHAIN_APPROX_SIMPLE);
            // 从表格的特性看，如果这片区域的点数小于4，那就代表没有一个完整的表格，忽略掉
            if (type == 0 && table_contours.size() < 4) {
                continue;
            } else if (type == 1 && table_contours.size() > 4) {
                continue;
            }
            // 将表格添加到集合
            tables.add(srcImage.submat(boundRect[i]).clone());
        }
        return tables;
    }
}
